package com.ld.userpay.service.handler.impl;

import java.io.IOException;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.enums.TradeType;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.IpKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.model.RefundModel;
import com.ijpay.wxpay.model.UnifiedOrderModel;
import com.ld.shieldsb.common.core.model.Result;
import com.ld.shieldsb.common.core.reflect.ClassUtil;
import com.ld.shieldsb.common.core.util.ResultUtil;
import com.ld.shieldsb.common.core.util.StringUtils;
import com.ld.shieldsb.common.core.util.date.DateUtil;
import com.ld.shieldsb.common.web.util.Web;
import com.ld.shieldsb.common.web.util.WebUtil;
import com.ld.userpay.model.OrderModel;
import com.ld.userpay.model.config.H5SceneInfo;
import com.ld.userpay.model.config.WxPayBean;
import com.ld.userpay.service.IOrderService;
import com.ld.userpay.service.handler.IPayHandler;
import com.ld.userpay.util.pay.PayUtil;
import com.ld.userpay.util.pay.PayUtil.PayType;

import lombok.extern.slf4j.Slf4j;

/**
 * 微信支付处理器
 * 
 * @ClassName WxPayHandler
 * @author <a href="mailto:donggongai@126.com" target="_blank">吕凯</a>
 * @date 2022年4月25日 下午4:11:20
 *
 */
@Slf4j
@Service
public class WxPayHandler implements IPayHandler {
    @Autowired
    protected WxPayBean wxPayBean; // 微信支付参数
    @Autowired
    protected IOrderService chargeService; // 支付服务类，需要在具体项目中实现

    /**
     * 二维码支付的网站，需要直接实现二维码的生成，weixin://wxpay/bizpayurl?pr=87csiY9zz
     * 
     * @Title getCodeUrl
     * @author 吕凯
     * @date 2022年4月27日 上午11:16:19
     * @param request
     * @param response
     * @param orderid
     * @param body
     * @param attach
     * @return
     * @see com.ld.userpay.service.handler.IPayHandler#getCodeUrl(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse, java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public Result getCodeUrl(HttpServletRequest request, HttpServletResponse response, String orderid, String body, String attach) {
        if (StringUtils.isBlank(orderid)) {
            return ResultUtil.error("订单号不能为空");
        }

        String ip = WebUtil.getIpAddr(request);

        // 查询订单钱数，注意这个是需要根据订单号查询的，不能直接传值，可能被篡改
        OrderModel financeModel = chargeService.getOrderInfo(orderid);
        // 钱数单位是分
        Double payout = financeModel.getPayMoney(); // 这个钱数单位是元
        // 注意调用工具类获取实际支付钱数
        Double payoutFen = PayUtil.getPayMoney(payout, request) * 100;
        // 需要转成整数
        String money = payoutFen.intValue() + "";

        Map<String, String> params = UnifiedOrderModel.builder().appid(wxPayBean.getAppId()).mch_id(wxPayBean.getMchId())
                .nonce_str(WxPayKit.generateStr()).body(body).attach(attach).out_trade_no(orderid).total_fee(money).spbill_create_ip(ip)
                .notify_url(wxPayBean.getDomain().concat(getNotifyUrl())).trade_type(TradeType.NATIVE.getTradeType()).build()
                .createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);

        String xmlResult = WxPayApi.pushOrder(false, params);
        log.debug("统一下单:{}", xmlResult);
        log.debug("params：{}", params);

        Map<String, String> result = WxPayKit.xmlToMap(xmlResult);

        String returnCode = result.get("return_code");
        String returnMsg = result.get("return_msg");
        log.warn(returnMsg);
        if (!WxPayKit.codeIsOk(returnCode)) {
            return ResultUtil.error("error:" + returnMsg);
        }
        String resultCode = result.get("result_code");
        if (!WxPayKit.codeIsOk(resultCode)) {
            return ResultUtil.error("error:" + returnMsg);
        }
        // 生成预付订单success

        String qrCodeUrl = result.get("code_url");

        // 在页面上显示
        return ResultUtil.success("", qrCodeUrl);
    }

    @Override
    public PayType type() {
        return PayType.WXPAY;
    }

    @Override
    public Result pcPay(HttpServletRequest request, HttpServletResponse response, String orderid, String body, String attach) {
        return ResultUtil.error("未实现");
    }

    /**
     * 微信的H5支付，注意：必须在web页面中发起支付且发起支付的域名已添加到开发配置中，否则会报“商家参数格式有误，请联系商家解决”
     * 
     * @Title wapPay
     * @author 吕凯
     * @date 2022年4月27日 下午4:33:48
     * @param request
     * @param response
     * @param orderid
     * @param body
     * @param attach
     * @return
     * @see com.ld.userpay.service.handler.IPayHandler#wapPay(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse,
     *      java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public Result wapPay(HttpServletRequest request, HttpServletResponse response, String orderid, String body, String attach) {
        String ip = WebUtil.getIpAddr(request);
        log.warn("ip {} ,WebUtil: {}", IpKit.getRealIp(request), ip);
        if (StringUtils.isBlank(ip)) {
            ip = "127.0.0.1";
        }

        H5SceneInfo sceneInfo = new H5SceneInfo();

        H5SceneInfo.H5 h5Info = new H5SceneInfo.H5();

        // 查询订单钱数，注意这个是需要根据订单号查询的，不能直接传值，可能被篡改
        OrderModel financeModel = chargeService.getOrderInfo(orderid);
        // 钱数单位是分
        Double payout = financeModel.getPayMoney(); // 这个钱数单位是元
        // 注意调用工具类获取实际支付钱数
        Double payoutFen = PayUtil.getPayMoney(payout, request) * 100;
        // 需要转成整数
        String money = payoutFen.intValue() + "";
        h5Info.setType("Wap");
        // 此域名必须在商户平台--"产品中心"--"开发配置"中添加
//        h5Info.setWap_url("https://www.miit5000.org.cn");
        h5Info.setWap_name(body);
        sceneInfo.setH5Info(h5Info);

        Map<String, String> params = UnifiedOrderModel.builder().appid(wxPayBean.getAppId()).mch_id(wxPayBean.getMchId())
                .nonce_str(WxPayKit.generateStr()).body(body).attach(attach).out_trade_no(orderid).total_fee(money).spbill_create_ip(ip)
                .notify_url(wxPayBean.getDomain().concat(getNotifyUrl())).trade_type(TradeType.MWEB.getTradeType())
                .scene_info(JSON.toJSONString(sceneInfo)).build().createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);

        String xmlResult = WxPayApi.pushOrder(false, params);
        log.info(xmlResult);

        Map<String, String> result = WxPayKit.xmlToMap(xmlResult);

        String returnCode = result.get("return_code");
        String returnMsg = result.get("return_msg");
        if (!WxPayKit.codeIsOk(returnCode)) {
            return ResultUtil.error(returnMsg);
        }
        String resultCode = result.get("result_code");
        if (!WxPayKit.codeIsOk(resultCode)) {
            return ResultUtil.error(returnMsg);
        }
        // 以下字段在return_code 和result_code都为SUCCESS的时候有返回

        String prepayId = result.get("prepay_id");
        String webUrl = result.get("mweb_url");

        log.debug("prepay_id:" + prepayId + " mweb_url:" + webUrl);

        try {
            response.sendRedirect(webUrl);
        } catch (IOException e) {
            log.error("", e);
            return ResultUtil.error(e.getMessage());
        }
        return ResultUtil.success("");
    }

    /**
     * 异步通知
     * 
     * @Title payNotify
     * @author 吕凯
     * @date 2022年4月24日 上午11:15:54
     * @param request
     * @param response
     * @param type
     *            void
     */
    @Override
    public void payNotify(HttpServletRequest request, HttpServletResponse response) {
        // 微信 POST

        String flag = "";
        String xmlMsg = HttpKit.readData(request);

        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
        log.warn("支付通知{} params：{}", params);
        /*{nonce_str=bda6923c3d02421591fe14bbd567de7d, sign=D5C9E033C0E7DF8A7F3AD1A2A1A41C5D833A316AB9C7B99C2A4ED6CECF64887A, 
         fee_type=CNY, mch_id=1616884378, cash_fee=1, total_fee=1, attach=充值, time_end=20220426161626, 
         transaction_id=4200001366202204266812654314, bank_type=OTHERS, openid=oyKLV5oPWdO5hFeJH5U9RIcS-MCg, 
         out_trade_no=GM20220426161518154672, appid=wxc03329626a12776a, trade_type=NATIVE, result_code=SUCCESS, 
         is_subscribe=N, return_code=SUCCESS}    */

        String returnCode = params.get("return_code");
        String outTradeNo = params.get("out_trade_no");
        String transactionId = params.get("transaction_id");
        String totalFeeStr = params.get("total_fee");
        String timeEnd = params.get("time_end");

        // 注意重复通知的情况，同一订单号可能收到多次通知，请注意一定先判断订单状态
        // 注意此处签名方式需与统一下单的签名类型一致
        if (WxPayKit.verifyNotify(params, wxPayBean.getPartnerKey(), SignType.HMACSHA256)) {
            if (WxPayKit.codeIsOk(returnCode)) {
                // 更新订单信息
                Double totalFee = ClassUtil.obj2doulbe(totalFeeStr, null) / 100; // 元转分
                chargeService.payNotifyCallback(true, outTradeNo, transactionId, PayUtil.PayType.WXPAY.getKeyword(), totalFee,
                        DateUtil.str2Date(timeEnd, "yyyyMMddHHmmss"), WebUtil.getIpAddr(request));

                // 发送通知等
                Map<String, String> xml = new HashMap<>(2);
                xml.put("return_code", "SUCCESS");
                xml.put("return_msg", "OK");
                log.debug("回调成功！{}", xml);
                flag = WxPayKit.toXml(xml);
            }
        }
        Web.Response.write(response, flag); // 直接返回字符串会加双引号，检验不通过

    }

    @Override
    public Pair<Result, Map<String, String>> payReturn(HttpServletRequest request, HttpServletResponse response) {
        return Pair.of(ResultUtil.error("未实现"), null);
    }

    /**
     * 
     * 微信退款，V2需要用证书，V3不确定
     * 
     * @Title refund
     * @author 吕凯
     * @date 2022年4月27日 上午11:12:18
     * @param outTradeNo
     *            外部订单号
     * @param transactionId
     *            微信订单号
     * @param monny
     *            退款钱数
     * @return
     * @see com.ld.userpay.service.handler.IPayHandler#refund(java.lang.String, java.lang.String, java.lang.Double)
     */
    @Override
    public Result refund(HttpServletRequest request, HttpServletResponse response, String outTradeNo, String transactionId, Double monny) {
        try {
            log.info("transactionId: {} outTradeNo:{}", transactionId, outTradeNo);

            if (StringUtils.isBlank(outTradeNo) && StringUtils.isBlank(transactionId)) {
                ResultUtil.error("transactionId、out_trade_no二选一");
            }
            // 单位是分
            Double payoutFen = monny * 100;
            String moneyStr = payoutFen.intValue() + "";

            Map<String, String> params = RefundModel.builder().appid(wxPayBean.getAppId()).mch_id(wxPayBean.getMchId())
                    .nonce_str(WxPayKit.generateStr()).transaction_id(transactionId).out_trade_no(outTradeNo)
                    .out_refund_no(WxPayKit.generateStr()).total_fee(moneyStr).refund_fee(moneyStr).refund_desc("正常退款").build()
                    .createSign(wxPayBean.getPartnerKey(), SignType.MD5);
            URL certUrl = Thread.currentThread().getContextClassLoader().getResource(wxPayBean.getCertPath());
            String certPath = null;
            if (certUrl != null) {
                certPath = certUrl.getPath();
            }

            String refundStr = WxPayApi.orderRefund(false, params, certPath, wxPayBean.getMchId());

            /*<xml><return_code><![CDATA[SUCCESS]]></return_code>
            <return_msg><![CDATA[OK]]></return_msg>
            <appid><![CDATA[wxc03329626a12776a]]></appid>
            <mch_id><![CDATA[1616884378]]></mch_id>
            <nonce_str><![CDATA[Aym2ibCXtaw5KEX2]]></nonce_str>
            <sign><![CDATA[E5FF61B059442927105853C168FFA58F]]></sign>
            <result_code><![CDATA[SUCCESS]]></result_code>
            <transaction_id><![CDATA[4200001366202204266812654314]]></transaction_id>
            <out_trade_no><![CDATA[GM20220426161518154672]]></out_trade_no>
            <out_refund_no><![CDATA[07bf2c0343dd45739ec940fd53cc9334]]></out_refund_no>
            <refund_id><![CDATA[50301801832022042619772063810]]></refund_id>
            <refund_channel><![CDATA[]]></refund_channel>
            <refund_fee>1</refund_fee>
            <coupon_refund_fee>0</coupon_refund_fee>
            <total_fee>1</total_fee>
            <cash_fee>1</cash_fee>
            <coupon_refund_count>0</coupon_refund_count>
            <cash_refund_fee>1</cash_refund_fee>
            </xml>*/
            Map<String, String> result = WxPayKit.xmlToMap(refundStr);
            String returnCode = result.get("return_code");
            String totalFeeStr = result.get("refund_fee");

            log.debug("refundStr: {}", refundStr);
            if ("SUCCESS".equals(returnCode)) {
                Double totalFee = ClassUtil.obj2doulbe(totalFeeStr, null) / 100; // 元转分
                // 业务系统的回调方法
                chargeService.refundCallback(true, outTradeNo, transactionId, PayUtil.PayType.WXPAY.getKeyword(), totalFee, new Date(),
                        WebUtil.getIpAddr(request));

                return ResultUtil.success("", result);
            }
            return ResultUtil.error("退款失败！", result);
        } catch (Exception e) {
            log.error("退款失败！", e);
        }
        return ResultUtil.error("退款失败！");
    }

}
